﻿using com.zlyd.utils;
using CopyRightCodeCollector.Collectors;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace CopyRightCodeCollector {
    public class Collector : IDisposable {

        readonly string SrcDir;
        readonly string DestDir;
        readonly string EntryPoint;
        readonly string ConfigFile;

        readonly bool noFileName;

        readonly HashSet<string> Includes;
        readonly HashSet<string> Excludes;

        readonly HeaderCollector headerCollector;
        readonly TailCollector tailCollector;
        readonly TotalLineCollector totalLineCollector;

        readonly Action<string, IEnumerable<string>> Outputer;

        public Collector(string srcDir, string destDir, string entryPoint, string configFile, Action<string, IEnumerable<string>> outputer) {
            this.SrcDir = srcDir;
            this.DestDir = destDir;
            this.EntryPoint = entryPoint;
            this.ConfigFile = configFile;
            this.Outputer = outputer;
            var config = YamlKit.ParseFile<JObject>(this.ConfigFile);

            this.noFileName = config.Value<bool>("noFileName");

            var includes = config.Value<JArray>("includes").Select(x => x.Value<string>());
            var excludes = config.Value<JArray>("excludes").Select(x => x.Value<string>());
            this.Includes = new(includes);
            this.Excludes = new(excludes);

            int headerLines = config.Value<int>("headerLines");
            int tailLines = config.Value<int>("tailLines");
            var headerPath = Path.Combine(this.DestDir, "header.txt");
            var tailPath = Path.Combine(this.DestDir, "tail.txt");
            var totalPath = Path.Combine(this.DestDir, "total.txt");
            this.headerCollector = new(headerPath, headerLines, this.Outputer);
            this.tailCollector = new(tailPath, tailLines, this.Outputer);
            this.totalLineCollector = new(totalPath, this.Outputer);
        }

        private bool ShouldInclude(string filepath)
            => FileRangeIndicator.MatchAnyRule(filepath, this.Includes);

        private bool ShouldExclude(string filepath)
            => FileRangeIndicator.MatchAnyRule(filepath, this.Excludes);

        private void AddLine(string line) {
            this.headerCollector.Add(line);
            this.tailCollector.Add(line);
            this.totalLineCollector.Add(line);
        }

        private void AddLines(string[] lines) {
            foreach (var line in lines) {
                this.AddLine(line);
            }
        }

        private void HandleFile(string filepath, bool skipEntryPoint = true) {
            if (!this.ShouldInclude(filepath)) return;
            if (this.ShouldExclude(filepath)) return;

            var relativePath = Path.GetRelativePath(this.SrcDir, filepath);
            if (skipEntryPoint && relativePath == this.EntryPoint)
                return;

            var lines = File.ReadAllLines(filepath);
            if (!this.noFileName)
                this.AddLine(string.Format($"-------- {relativePath} --------"));
            this.AddLines(lines);
            this.AddLine(string.Empty);
        }

        private void Dig(string folder) {
            var files = Directory.GetFiles(folder);
            foreach (var file in files) {
                this.HandleFile(file);
            }

            var subs = Directory.GetDirectories(folder);
            foreach (var sub in subs) {
                if (this.ShouldExclude(sub)) continue;
                this.Dig(sub);
            }
        }

        public void Work() {
            if (this.EntryPoint is not null) {
                var entryPoint = Path.Combine(this.SrcDir, this.EntryPoint);
                this.HandleFile(entryPoint, skipEntryPoint: false);
            }
            this.Dig(this.SrcDir);
        }

        public void Dispose() {
            this.headerCollector.Dispose();
            this.tailCollector.Dispose();
            this.totalLineCollector.Dispose();
        }
    }
}
